You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
go-library/vendor/go.mongodb.org/mongo-driver/x/mongo/driver/operation/hello.go

259 lines
8.5 KiB

// Copyright (C) MongoDB, Inc. 2021-present.
//
// 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
package operation
import (
"context"
"errors"
"runtime"
"strconv"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/internal"
"go.mongodb.org/mongo-driver/mongo/address"
"go.mongodb.org/mongo-driver/mongo/description"
"go.mongodb.org/mongo-driver/version"
"go.mongodb.org/mongo-driver/x/bsonx/bsoncore"
"go.mongodb.org/mongo-driver/x/mongo/driver"
"go.mongodb.org/mongo-driver/x/mongo/driver/session"
)
// Hello is used to run the handshake operation.
type Hello struct {
appname string
compressors []string
saslSupportedMechs string
d driver.Deployment
clock *session.ClusterClock
speculativeAuth bsoncore.Document
topologyVersion *description.TopologyVersion
maxAwaitTimeMS *int64
serverAPI *driver.ServerAPIOptions
loadBalanced bool
res bsoncore.Document
}
var _ driver.Handshaker = (*Hello)(nil)
// NewHello constructs a Hello.
func NewHello() *Hello { return &Hello{} }
// AppName sets the application name in the client metadata sent in this operation.
func (h *Hello) AppName(appname string) *Hello {
h.appname = appname
return h
}
// ClusterClock sets the cluster clock for this operation.
func (h *Hello) ClusterClock(clock *session.ClusterClock) *Hello {
if h == nil {
h = new(Hello)
}
h.clock = clock
return h
}
// Compressors sets the compressors that can be used.
func (h *Hello) Compressors(compressors []string) *Hello {
h.compressors = compressors
return h
}
// SASLSupportedMechs retrieves the supported SASL mechanism for the given user when this operation
// is run.
func (h *Hello) SASLSupportedMechs(username string) *Hello {
h.saslSupportedMechs = username
return h
}
// Deployment sets the Deployment for this operation.
func (h *Hello) Deployment(d driver.Deployment) *Hello {
h.d = d
return h
}
// SpeculativeAuthenticate sets the document to be used for speculative authentication.
func (h *Hello) SpeculativeAuthenticate(doc bsoncore.Document) *Hello {
h.speculativeAuth = doc
return h
}
// TopologyVersion sets the TopologyVersion to be used for heartbeats.
func (h *Hello) TopologyVersion(tv *description.TopologyVersion) *Hello {
h.topologyVersion = tv
return h
}
// MaxAwaitTimeMS sets the maximum time for the server to wait for topology changes during a heartbeat.
func (h *Hello) MaxAwaitTimeMS(awaitTime int64) *Hello {
h.maxAwaitTimeMS = &awaitTime
return h
}
// ServerAPI sets the server API version for this operation.
func (h *Hello) ServerAPI(serverAPI *driver.ServerAPIOptions) *Hello {
h.serverAPI = serverAPI
return h
}
// LoadBalanced specifies whether or not this operation is being sent over a connection to a load balanced cluster.
func (h *Hello) LoadBalanced(lb bool) *Hello {
h.loadBalanced = lb
return h
}
// Result returns the result of executing this operation.
func (h *Hello) Result(addr address.Address) description.Server {
return description.NewServer(addr, bson.Raw(h.res))
}
// handshakeCommand appends all necessary command fields as well as client metadata, SASL supported mechs, and compression.
func (h *Hello) handshakeCommand(dst []byte, desc description.SelectedServer) ([]byte, error) {
dst, err := h.command(dst, desc)
if err != nil {
return dst, err
}
if h.saslSupportedMechs != "" {
dst = bsoncore.AppendStringElement(dst, "saslSupportedMechs", h.saslSupportedMechs)
}
if h.speculativeAuth != nil {
dst = bsoncore.AppendDocumentElement(dst, "speculativeAuthenticate", h.speculativeAuth)
}
var idx int32
idx, dst = bsoncore.AppendArrayElementStart(dst, "compression")
for i, compressor := range h.compressors {
dst = bsoncore.AppendStringElement(dst, strconv.Itoa(i), compressor)
}
dst, _ = bsoncore.AppendArrayEnd(dst, idx)
// append client metadata
idx, dst = bsoncore.AppendDocumentElementStart(dst, "client")
didx, dst := bsoncore.AppendDocumentElementStart(dst, "driver")
dst = bsoncore.AppendStringElement(dst, "name", "mongo-go-driver")
dst = bsoncore.AppendStringElement(dst, "version", version.Driver)
dst, _ = bsoncore.AppendDocumentEnd(dst, didx)
didx, dst = bsoncore.AppendDocumentElementStart(dst, "os")
dst = bsoncore.AppendStringElement(dst, "type", runtime.GOOS)
dst = bsoncore.AppendStringElement(dst, "architecture", runtime.GOARCH)
dst, _ = bsoncore.AppendDocumentEnd(dst, didx)
dst = bsoncore.AppendStringElement(dst, "platform", runtime.Version())
if h.appname != "" {
didx, dst = bsoncore.AppendDocumentElementStart(dst, "application")
dst = bsoncore.AppendStringElement(dst, "name", h.appname)
dst, _ = bsoncore.AppendDocumentEnd(dst, didx)
}
dst, _ = bsoncore.AppendDocumentEnd(dst, idx)
return dst, nil
}
// command appends all necessary command fields.
func (h *Hello) command(dst []byte, desc description.SelectedServer) ([]byte, error) {
// Use "hello" if topology is LoadBalanced, API version is declared or server
// has responded with "helloOk". Otherwise, use legacy hello.
if desc.Kind == description.LoadBalanced || h.serverAPI != nil || desc.Server.HelloOK {
dst = bsoncore.AppendInt32Element(dst, "hello", 1)
} else {
dst = bsoncore.AppendInt32Element(dst, internal.LegacyHello, 1)
}
dst = bsoncore.AppendBooleanElement(dst, "helloOk", true)
if tv := h.topologyVersion; tv != nil {
var tvIdx int32
tvIdx, dst = bsoncore.AppendDocumentElementStart(dst, "topologyVersion")
dst = bsoncore.AppendObjectIDElement(dst, "processId", tv.ProcessID)
dst = bsoncore.AppendInt64Element(dst, "counter", tv.Counter)
dst, _ = bsoncore.AppendDocumentEnd(dst, tvIdx)
}
if h.maxAwaitTimeMS != nil {
dst = bsoncore.AppendInt64Element(dst, "maxAwaitTimeMS", *h.maxAwaitTimeMS)
}
if h.loadBalanced {
// The loadBalanced parameter should only be added if it's true. We should never explicitly send
// loadBalanced=false per the load balancing spec.
dst = bsoncore.AppendBooleanElement(dst, "loadBalanced", true)
}
return dst, nil
}
// Execute runs this operation.
func (h *Hello) Execute(ctx context.Context) error {
if h.d == nil {
return errors.New("a Hello must have a Deployment set before Execute can be called")
}
return h.createOperation().Execute(ctx)
}
// StreamResponse gets the next streaming Hello response from the server.
func (h *Hello) StreamResponse(ctx context.Context, conn driver.StreamerConnection) error {
return h.createOperation().ExecuteExhaust(ctx, conn)
}
func (h *Hello) createOperation() driver.Operation {
return driver.Operation{
Clock: h.clock,
CommandFn: h.command,
Database: "admin",
Deployment: h.d,
ProcessResponseFn: func(info driver.ResponseInfo) error {
h.res = info.ServerResponse
return nil
},
ServerAPI: h.serverAPI,
}
}
// GetHandshakeInformation performs the MongoDB handshake for the provided connection and returns the relevant
// information about the server. This function implements the driver.Handshaker interface.
func (h *Hello) GetHandshakeInformation(ctx context.Context, _ address.Address, c driver.Connection) (driver.HandshakeInformation, error) {
err := driver.Operation{
Clock: h.clock,
CommandFn: h.handshakeCommand,
Deployment: driver.SingleConnectionDeployment{c},
Database: "admin",
ProcessResponseFn: func(info driver.ResponseInfo) error {
h.res = info.ServerResponse
return nil
},
ServerAPI: h.serverAPI,
}.Execute(ctx)
if err != nil {
return driver.HandshakeInformation{}, err
}
info := driver.HandshakeInformation{
Description: h.Result(c.Address()),
}
if speculativeAuthenticate, ok := h.res.Lookup("speculativeAuthenticate").DocumentOK(); ok {
info.SpeculativeAuthenticate = speculativeAuthenticate
}
if serverConnectionID, ok := h.res.Lookup("connectionId").AsInt64OK(); ok {
info.ServerConnectionID = &serverConnectionID
}
// Cast to bson.Raw to lookup saslSupportedMechs to avoid converting from bsoncore.Value to bson.RawValue for the
// StringSliceFromRawValue call.
if saslSupportedMechs, lookupErr := bson.Raw(h.res).LookupErr("saslSupportedMechs"); lookupErr == nil {
info.SaslSupportedMechs, err = internal.StringSliceFromRawValue("saslSupportedMechs", saslSupportedMechs)
}
return info, err
}
// FinishHandshake implements the Handshaker interface. This is a no-op function because a non-authenticated connection
// does not do anything besides the initial Hello for a handshake.
func (h *Hello) FinishHandshake(context.Context, driver.Connection) error {
return nil
}