Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 49 additions & 12 deletions internal/integration/handshake_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"go.mongodb.org/mongo-driver/v2/internal/handshake"
"go.mongodb.org/mongo-driver/v2/internal/integration/mtest"
"go.mongodb.org/mongo-driver/v2/internal/require"
"go.mongodb.org/mongo-driver/v2/mongo/options"
"go.mongodb.org/mongo-driver/v2/version"
"go.mongodb.org/mongo-driver/v2/x/bsonx/bsoncore"
"go.mongodb.org/mongo-driver/v2/x/mongo/driver/wiremessage"
Expand All @@ -34,19 +35,31 @@ func TestHandshakeProse(t *testing.T) {
CreateCollection(false).
ClientType(mtest.Proxy)

clientMetadata := func(env bson.D) bson.D {
clientMetadata := func(env bson.D, info *options.DriverInfo) bson.D {
var (
driverName = "mongo-go-driver"
driverVersion = version.Driver
platform = runtime.Version()
)

if info != nil {
driverName = driverName + "|" + info.Name
driverVersion = driverVersion + "|" + info.Version
platform = platform + "|" + info.Platform
}

elems := bson.D{
{Key: "driver", Value: bson.D{
{Key: "name", Value: "mongo-go-driver"},
{Key: "version", Value: version.Driver},
{Key: "name", Value: driverName},
{Key: "version", Value: driverVersion},
}},
{Key: "os", Value: bson.D{
{Key: "type", Value: runtime.GOOS},
{Key: "architecture", Value: runtime.GOARCH},
}},
}

elems = append(elems, bson.E{Key: "platform", Value: runtime.Version()})
elems = append(elems, bson.E{Key: "platform", Value: platform})

// If env is empty, don't include it in the metadata.
if env != nil && !reflect.DeepEqual(env, bson.D{}) {
Expand All @@ -56,6 +69,12 @@ func TestHandshakeProse(t *testing.T) {
return elems
}

driverInfo := &options.DriverInfo{
Name: "outer-library-name",
Version: "outer-library-version",
Platform: "outer-library-platform",
}

// Reset the environment variables to avoid environment namespace
// collision.
t.Setenv("AWS_EXECUTION_ENV", "")
Expand All @@ -72,6 +91,7 @@ func TestHandshakeProse(t *testing.T) {
for _, test := range []struct {
name string
env map[string]string
opts *options.ClientOptionsBuilder
want bson.D
}{
{
Expand All @@ -81,20 +101,22 @@ func TestHandshakeProse(t *testing.T) {
"AWS_REGION": "us-east-2",
"AWS_LAMBDA_FUNCTION_MEMORY_SIZE": "1024",
},
opts: nil,
want: clientMetadata(bson.D{
{Key: "name", Value: "aws.lambda"},
{Key: "memory_mb", Value: 1024},
{Key: "region", Value: "us-east-2"},
}),
}, nil),
},
{
name: "2. valid Azure",
env: map[string]string{
"FUNCTIONS_WORKER_RUNTIME": "node",
},
opts: nil,
want: clientMetadata(bson.D{
{Key: "name", Value: "azure.func"},
}),
}, nil),
},
{
name: "3. valid GCP",
Expand All @@ -104,31 +126,34 @@ func TestHandshakeProse(t *testing.T) {
"FUNCTION_TIMEOUT_SEC": "60",
"FUNCTION_REGION": "us-central1",
},
opts: nil,
want: clientMetadata(bson.D{
{Key: "name", Value: "gcp.func"},
{Key: "memory_mb", Value: 1024},
{Key: "region", Value: "us-central1"},
{Key: "timeout_sec", Value: 60},
}),
}, nil),
},
{
name: "4. valid Vercel",
env: map[string]string{
"VERCEL": "1",
"VERCEL_REGION": "cdg1",
},
opts: nil,
want: clientMetadata(bson.D{
{Key: "name", Value: "vercel"},
{Key: "region", Value: "cdg1"},
}),
}, nil),
},
{
name: "5. invalid multiple providers",
env: map[string]string{
"AWS_EXECUTION_ENV": "AWS_Lambda_java8",
"FUNCTIONS_WORKER_RUNTIME": "node",
},
want: clientMetadata(nil),
opts: nil,
want: clientMetadata(nil, nil),
},
{
name: "6. invalid long string",
Expand All @@ -142,26 +167,34 @@ func TestHandshakeProse(t *testing.T) {
return s
}(),
},
opts: nil,
want: clientMetadata(bson.D{
{Key: "name", Value: "aws.lambda"},
}),
}, nil),
},
{
name: "7. invalid wrong types",
env: map[string]string{
"AWS_EXECUTION_ENV": "AWS_Lambda_java8",
"AWS_LAMBDA_FUNCTION_MEMORY_SIZE": "big",
},
opts: nil,
want: clientMetadata(bson.D{
{Key: "name", Value: "aws.lambda"},
}),
}, nil),
},
{
name: "8. Invalid - AWS_EXECUTION_ENV does not start with \"AWS_Lambda_\"",
env: map[string]string{
"AWS_EXECUTION_ENV": "EC2",
},
want: clientMetadata(nil),
opts: nil,
want: clientMetadata(nil, nil),
},
{
name: "driver info included",
opts: options.Client().SetDriverInfo(driverInfo),
want: clientMetadata(nil, driverInfo),
},
} {
test := test
Expand All @@ -171,6 +204,10 @@ func TestHandshakeProse(t *testing.T) {
mt.Setenv(k, v)
}

if test.opts != nil {
mt.ResetClient(test.opts)
}

// Ping the server to ensure the handshake has completed.
err := mt.Client.Ping(context.Background(), nil)
require.NoError(mt, err, "Ping error: %v", err)
Expand Down
25 changes: 25 additions & 0 deletions mongo/options/clientoptions.go
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,17 @@ type BSONOptions struct {
ZeroStructs bool
}

// DriverInfo appends the client metadata generated by the driver when
// handshaking the server. These options do not replace the values used
// during the handshake, rather they are deliminated with a | with the
// driver-generated data. This should be used by libraries wrapping the driver,
// e.g. ODMs.
type DriverInfo struct {
Name string // Name of the library wrapping the driver.
Version string // Version of the library wrapping the driver.
Platform string // Platform information for the wrapping driver.
}

// ClientOptions contains arguments to configure a Client instance. Arguments
// can be set through the ClientOptions setter functions. See each function for
// documentation.
Expand All @@ -235,6 +246,7 @@ type ClientOptions struct {
Dialer ContextDialer
Direct *bool
DisableOCSPEndpointCheck *bool
DriverInfo *DriverInfo
HeartbeatInterval *time.Duration
Hosts []string
HTTPClient *http.Client
Expand Down Expand Up @@ -1249,6 +1261,19 @@ func (c *ClientOptionsBuilder) SetSRVServiceName(srvName string) *ClientOptionsB
return c
}

// SetDriverInfo configures optional data to include in the handshake's client
// metadata, delimited by "|" with the driver-generated data. This should be
// used by libraries wrapping the driver, e.g. ODMs.
func (c *ClientOptionsBuilder) SetDriverInfo(info *DriverInfo) *ClientOptionsBuilder {
c.Opts = append(c.Opts, func(opts *ClientOptions) error {
opts.DriverInfo = info

return nil
})

return c
}

// addCACertFromFile adds a root CA certificate to the configuration given a path
// to the containing file.
func addCACertFromFile(cfg *tls.Config, file string) error {
Expand Down
10 changes: 9 additions & 1 deletion x/mongo/driver/auth/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,11 @@ type HandshakeOptions struct {
ClusterClock *session.ClusterClock
ServerAPI *driver.ServerAPIOptions
LoadBalanced bool

// Fields provided by a library that wraps the Go Driver.
OuterLibraryName string
OuterLibraryVersion string
OuterLibraryPlatform string
}

type authHandshaker struct {
Expand Down Expand Up @@ -94,7 +99,10 @@ func (ah *authHandshaker) GetHandshakeInformation(
SASLSupportedMechs(ah.options.DBUser).
ClusterClock(ah.options.ClusterClock).
ServerAPI(ah.options.ServerAPI).
LoadBalanced(ah.options.LoadBalanced)
LoadBalanced(ah.options.LoadBalanced).
OuterLibraryName(ah.options.OuterLibraryName).
OuterLibraryVersion(ah.options.OuterLibraryVersion).
OuterLibraryPlatform(ah.options.OuterLibraryPlatform)

if ah.options.Authenticator != nil {
if speculativeAuth, ok := ah.options.Authenticator.(SpeculativeAuthenticator); ok {
Expand Down
63 changes: 53 additions & 10 deletions x/mongo/driver/operation/hello.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,11 @@ type Hello struct {
loadBalanced bool
omitMaxTimeMS bool

// Fields provided by a library that wraps the Go Driver.
outerLibraryName string
outerLibraryVersion string
outerLibraryPlatform string

res bsoncore.Document
}

Expand Down Expand Up @@ -123,6 +128,29 @@ func (h *Hello) LoadBalanced(lb bool) *Hello {
return h
}

// OuterLibraryName specifies the name of the library wrapping the Go Driver.
func (h *Hello) OuterLibraryName(name string) *Hello {
h.outerLibraryName = name

return h
}

// OuterLibraryVersion specifies the version of the library wrapping the Go
// Driver.
func (h *Hello) OuterLibraryVersion(version string) *Hello {
h.outerLibraryVersion = version

return h
}

// OuterLibraryPlatform specifies the platform of the library wrapping the Go
// Driver.
func (h *Hello) OuterLibraryPlatform(platform string) *Hello {
h.outerLibraryPlatform = platform

return h
}

// Result returns the result of executing this operation.
func (h *Hello) Result(addr address.Address) description.Server {
return driverutil.NewServerDescription(addr, bson.Raw(h.res))
Expand Down Expand Up @@ -247,12 +275,22 @@ func appendClientAppName(dst []byte, name string) ([]byte, error) {
// appendClientDriver appends the driver metadata to dst. It is the
// responsibility of the caller to check that this appending does not cause dst
// to exceed any size limitations.
func appendClientDriver(dst []byte) ([]byte, error) {
func appendClientDriver(dst []byte, outerLibraryName, outerLibraryVersion string) ([]byte, error) {
var idx int32
idx, dst = bsoncore.AppendDocumentElementStart(dst, "driver")

dst = bsoncore.AppendStringElement(dst, "name", driverName)
dst = bsoncore.AppendStringElement(dst, "version", version.Driver)
name := driverName
if outerLibraryName != "" {
name = name + "|" + outerLibraryName
}

version := version.Driver
if outerLibraryVersion != "" {
version = version + "|" + outerLibraryVersion
}

dst = bsoncore.AppendStringElement(dst, "name", name)
dst = bsoncore.AppendStringElement(dst, "version", version)

return bsoncore.AppendDocumentEnd(dst, idx)
}
Expand Down Expand Up @@ -374,8 +412,13 @@ func appendClientOS(dst []byte, omitNonType bool) ([]byte, error) {
// appendClientPlatform appends the platform metadata to dst. It is the
// responsibility of the caller to check that this appending does not cause dst
// to exceed any size limitations.
func appendClientPlatform(dst []byte) []byte {
return bsoncore.AppendStringElement(dst, "platform", runtime.Version())
func appendClientPlatform(dst []byte, outerLibraryPlatform string) []byte {
platform := runtime.Version()
if outerLibraryPlatform != "" {
platform = platform + "|" + outerLibraryPlatform
}

return bsoncore.AppendStringElement(dst, "platform", platform)
}

// encodeClientMetadata encodes the client metadata into a BSON document. maxLen
Expand Down Expand Up @@ -412,7 +455,7 @@ func appendClientPlatform(dst []byte) []byte {
// }
// }
// }
func encodeClientMetadata(appname string, maxLen int) ([]byte, error) {
func encodeClientMetadata(h *Hello, maxLen int) ([]byte, error) {
dst := make([]byte, 0, maxLen)

omitEnvDoc := false
Expand All @@ -426,12 +469,12 @@ retry:
idx, dst = bsoncore.AppendDocumentStart(dst)

var err error
dst, err = appendClientAppName(dst, appname)
dst, err = appendClientAppName(dst, h.appname)
if err != nil {
return nil, err
}

dst, err = appendClientDriver(dst)
dst, err = appendClientDriver(dst, h.outerLibraryName, h.outerLibraryVersion)
if err != nil {
return nil, err
}
Expand All @@ -442,7 +485,7 @@ retry:
}

if !truncatePlatform {
dst = appendClientPlatform(dst)
dst = appendClientPlatform(dst, h.outerLibraryPlatform)
}

if !omitEnvDocument {
Expand Down Expand Up @@ -519,7 +562,7 @@ func (h *Hello) handshakeCommand(dst []byte, desc description.SelectedServer) ([
}
dst, _ = bsoncore.AppendArrayEnd(dst, idx)

clientMetadata, _ := encodeClientMetadata(h.appname, maxClientMetadataSize)
clientMetadata, _ := encodeClientMetadata(h, maxClientMetadataSize)

// If the client metadata is empty, do not append it to the command.
if len(clientMetadata) > 0 {
Expand Down
Loading
Loading