go mod vendor

+ move k8s.io/apimachinery fork from go.work to go.mod
(and include it in vendor)
This commit is contained in:
2022-11-07 00:16:27 +02:00
parent d08bbf250a
commit e45bf4739b
1366 changed files with 469062 additions and 45 deletions

View File

@@ -0,0 +1,11 @@
// Copyright (C) MongoDB, Inc. 2017-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 description contains types and functions for describing the state of MongoDB clusters.
package description // import "go.mongodb.org/mongo-driver/mongo/description"
// Unknown is an unknown server or topology kind.
const Unknown = 0

View File

@@ -0,0 +1,488 @@
// Copyright (C) MongoDB, Inc. 2017-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 description
import (
"errors"
"fmt"
"time"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
"go.mongodb.org/mongo-driver/internal"
"go.mongodb.org/mongo-driver/mongo/address"
"go.mongodb.org/mongo-driver/tag"
)
// SelectedServer augments the Server type by also including the TopologyKind of the topology that includes the server.
// This type should be used to track the state of a server that was selected to perform an operation.
type SelectedServer struct {
Server
Kind TopologyKind
}
// Server contains information about a node in a cluster. This is created from hello command responses. If the value
// of the Kind field is LoadBalancer, only the Addr and Kind fields will be set. All other fields will be set to the
// zero value of the field's type.
type Server struct {
Addr address.Address
Arbiters []string
AverageRTT time.Duration
AverageRTTSet bool
Compression []string // compression methods returned by server
CanonicalAddr address.Address
ElectionID primitive.ObjectID
HeartbeatInterval time.Duration
HelloOK bool
Hosts []string
IsCryptd bool
LastError error
LastUpdateTime time.Time
LastWriteTime time.Time
MaxBatchCount uint32
MaxDocumentSize uint32
MaxMessageSize uint32
Members []address.Address
Passives []string
Passive bool
Primary address.Address
ReadOnly bool
ServiceID *primitive.ObjectID // Only set for servers that are deployed behind a load balancer.
SessionTimeoutMinutes uint32
SetName string
SetVersion uint32
Tags tag.Set
TopologyVersion *TopologyVersion
Kind ServerKind
WireVersion *VersionRange
}
// NewServer creates a new server description from the given hello command response.
func NewServer(addr address.Address, response bson.Raw) Server {
desc := Server{Addr: addr, CanonicalAddr: addr, LastUpdateTime: time.Now().UTC()}
elements, err := response.Elements()
if err != nil {
desc.LastError = err
return desc
}
var ok bool
var isReplicaSet, isWritablePrimary, hidden, secondary, arbiterOnly bool
var msg string
var versionRange VersionRange
for _, element := range elements {
switch element.Key() {
case "arbiters":
var err error
desc.Arbiters, err = internal.StringSliceFromRawElement(element)
if err != nil {
desc.LastError = err
return desc
}
case "arbiterOnly":
arbiterOnly, ok = element.Value().BooleanOK()
if !ok {
desc.LastError = fmt.Errorf("expected 'arbiterOnly' to be a boolean but it's a BSON %s", element.Value().Type)
return desc
}
case "compression":
var err error
desc.Compression, err = internal.StringSliceFromRawElement(element)
if err != nil {
desc.LastError = err
return desc
}
case "electionId":
desc.ElectionID, ok = element.Value().ObjectIDOK()
if !ok {
desc.LastError = fmt.Errorf("expected 'electionId' to be a objectID but it's a BSON %s", element.Value().Type)
return desc
}
case "iscryptd":
desc.IsCryptd, ok = element.Value().BooleanOK()
if !ok {
desc.LastError = fmt.Errorf("expected 'iscryptd' to be a boolean but it's a BSON %s", element.Value().Type)
return desc
}
case "helloOk":
desc.HelloOK, ok = element.Value().BooleanOK()
if !ok {
desc.LastError = fmt.Errorf("expected 'helloOk' to be a boolean but it's a BSON %s", element.Value().Type)
return desc
}
case "hidden":
hidden, ok = element.Value().BooleanOK()
if !ok {
desc.LastError = fmt.Errorf("expected 'hidden' to be a boolean but it's a BSON %s", element.Value().Type)
return desc
}
case "hosts":
var err error
desc.Hosts, err = internal.StringSliceFromRawElement(element)
if err != nil {
desc.LastError = err
return desc
}
case "isWritablePrimary":
isWritablePrimary, ok = element.Value().BooleanOK()
if !ok {
desc.LastError = fmt.Errorf("expected 'isWritablePrimary' to be a boolean but it's a BSON %s", element.Value().Type)
return desc
}
case internal.LegacyHelloLowercase:
isWritablePrimary, ok = element.Value().BooleanOK()
if !ok {
desc.LastError = fmt.Errorf("expected legacy hello to be a boolean but it's a BSON %s", element.Value().Type)
return desc
}
case "isreplicaset":
isReplicaSet, ok = element.Value().BooleanOK()
if !ok {
desc.LastError = fmt.Errorf("expected 'isreplicaset' to be a boolean but it's a BSON %s", element.Value().Type)
return desc
}
case "lastWrite":
lastWrite, ok := element.Value().DocumentOK()
if !ok {
desc.LastError = fmt.Errorf("expected 'lastWrite' to be a document but it's a BSON %s", element.Value().Type)
return desc
}
dateTime, err := lastWrite.LookupErr("lastWriteDate")
if err == nil {
dt, ok := dateTime.DateTimeOK()
if !ok {
desc.LastError = fmt.Errorf("expected 'lastWriteDate' to be a datetime but it's a BSON %s", dateTime.Type)
return desc
}
desc.LastWriteTime = time.Unix(dt/1000, dt%1000*1000000).UTC()
}
case "logicalSessionTimeoutMinutes":
i64, ok := element.Value().AsInt64OK()
if !ok {
desc.LastError = fmt.Errorf("expected 'logicalSessionTimeoutMinutes' to be an integer but it's a BSON %s", element.Value().Type)
return desc
}
desc.SessionTimeoutMinutes = uint32(i64)
case "maxBsonObjectSize":
i64, ok := element.Value().AsInt64OK()
if !ok {
desc.LastError = fmt.Errorf("expected 'maxBsonObjectSize' to be an integer but it's a BSON %s", element.Value().Type)
return desc
}
desc.MaxDocumentSize = uint32(i64)
case "maxMessageSizeBytes":
i64, ok := element.Value().AsInt64OK()
if !ok {
desc.LastError = fmt.Errorf("expected 'maxMessageSizeBytes' to be an integer but it's a BSON %s", element.Value().Type)
return desc
}
desc.MaxMessageSize = uint32(i64)
case "maxWriteBatchSize":
i64, ok := element.Value().AsInt64OK()
if !ok {
desc.LastError = fmt.Errorf("expected 'maxWriteBatchSize' to be an integer but it's a BSON %s", element.Value().Type)
return desc
}
desc.MaxBatchCount = uint32(i64)
case "me":
me, ok := element.Value().StringValueOK()
if !ok {
desc.LastError = fmt.Errorf("expected 'me' to be a string but it's a BSON %s", element.Value().Type)
return desc
}
desc.CanonicalAddr = address.Address(me).Canonicalize()
case "maxWireVersion":
versionRange.Max, ok = element.Value().AsInt32OK()
if !ok {
desc.LastError = fmt.Errorf("expected 'maxWireVersion' to be an integer but it's a BSON %s", element.Value().Type)
return desc
}
case "minWireVersion":
versionRange.Min, ok = element.Value().AsInt32OK()
if !ok {
desc.LastError = fmt.Errorf("expected 'minWireVersion' to be an integer but it's a BSON %s", element.Value().Type)
return desc
}
case "msg":
msg, ok = element.Value().StringValueOK()
if !ok {
desc.LastError = fmt.Errorf("expected 'msg' to be a string but it's a BSON %s", element.Value().Type)
return desc
}
case "ok":
okay, ok := element.Value().AsInt32OK()
if !ok {
desc.LastError = fmt.Errorf("expected 'ok' to be a boolean but it's a BSON %s", element.Value().Type)
return desc
}
if okay != 1 {
desc.LastError = errors.New("not ok")
return desc
}
case "passives":
var err error
desc.Passives, err = internal.StringSliceFromRawElement(element)
if err != nil {
desc.LastError = err
return desc
}
case "passive":
desc.Passive, ok = element.Value().BooleanOK()
if !ok {
desc.LastError = fmt.Errorf("expected 'passive' to be a boolean but it's a BSON %s", element.Value().Type)
return desc
}
case "primary":
primary, ok := element.Value().StringValueOK()
if !ok {
desc.LastError = fmt.Errorf("expected 'primary' to be a string but it's a BSON %s", element.Value().Type)
return desc
}
desc.Primary = address.Address(primary)
case "readOnly":
desc.ReadOnly, ok = element.Value().BooleanOK()
if !ok {
desc.LastError = fmt.Errorf("expected 'readOnly' to be a boolean but it's a BSON %s", element.Value().Type)
return desc
}
case "secondary":
secondary, ok = element.Value().BooleanOK()
if !ok {
desc.LastError = fmt.Errorf("expected 'secondary' to be a boolean but it's a BSON %s", element.Value().Type)
return desc
}
case "serviceId":
oid, ok := element.Value().ObjectIDOK()
if !ok {
desc.LastError = fmt.Errorf("expected 'serviceId' to be an ObjectId but it's a BSON %s", element.Value().Type)
}
desc.ServiceID = &oid
case "setName":
desc.SetName, ok = element.Value().StringValueOK()
if !ok {
desc.LastError = fmt.Errorf("expected 'setName' to be a string but it's a BSON %s", element.Value().Type)
return desc
}
case "setVersion":
i64, ok := element.Value().AsInt64OK()
if !ok {
desc.LastError = fmt.Errorf("expected 'setVersion' to be an integer but it's a BSON %s", element.Value().Type)
return desc
}
desc.SetVersion = uint32(i64)
case "tags":
m, err := decodeStringMap(element, "tags")
if err != nil {
desc.LastError = err
return desc
}
desc.Tags = tag.NewTagSetFromMap(m)
case "topologyVersion":
doc, ok := element.Value().DocumentOK()
if !ok {
desc.LastError = fmt.Errorf("expected 'topologyVersion' to be a document but it's a BSON %s", element.Value().Type)
return desc
}
desc.TopologyVersion, err = NewTopologyVersion(doc)
if err != nil {
desc.LastError = err
return desc
}
}
}
for _, host := range desc.Hosts {
desc.Members = append(desc.Members, address.Address(host).Canonicalize())
}
for _, passive := range desc.Passives {
desc.Members = append(desc.Members, address.Address(passive).Canonicalize())
}
for _, arbiter := range desc.Arbiters {
desc.Members = append(desc.Members, address.Address(arbiter).Canonicalize())
}
desc.Kind = Standalone
if isReplicaSet {
desc.Kind = RSGhost
} else if desc.SetName != "" {
if isWritablePrimary {
desc.Kind = RSPrimary
} else if hidden {
desc.Kind = RSMember
} else if secondary {
desc.Kind = RSSecondary
} else if arbiterOnly {
desc.Kind = RSArbiter
} else {
desc.Kind = RSMember
}
} else if msg == "isdbgrid" {
desc.Kind = Mongos
}
desc.WireVersion = &versionRange
return desc
}
// NewDefaultServer creates a new unknown server description with the given address.
func NewDefaultServer(addr address.Address) Server {
return NewServerFromError(addr, nil, nil)
}
// NewServerFromError creates a new unknown server description with the given parameters.
func NewServerFromError(addr address.Address, err error, tv *TopologyVersion) Server {
return Server{
Addr: addr,
LastError: err,
Kind: Unknown,
TopologyVersion: tv,
}
}
// SetAverageRTT sets the average round trip time for this server description.
func (s Server) SetAverageRTT(rtt time.Duration) Server {
s.AverageRTT = rtt
s.AverageRTTSet = true
return s
}
// DataBearing returns true if the server is a data bearing server.
func (s Server) DataBearing() bool {
return s.Kind == RSPrimary ||
s.Kind == RSSecondary ||
s.Kind == Mongos ||
s.Kind == Standalone
}
// LoadBalanced returns true if the server is a load balancer or is behind a load balancer.
func (s Server) LoadBalanced() bool {
return s.Kind == LoadBalancer || s.ServiceID != nil
}
// String implements the Stringer interface
func (s Server) String() string {
str := fmt.Sprintf("Addr: %s, Type: %s",
s.Addr, s.Kind)
if len(s.Tags) != 0 {
str += fmt.Sprintf(", Tag sets: %s", s.Tags)
}
if s.AverageRTTSet {
str += fmt.Sprintf(", Average RTT: %d", s.AverageRTT)
}
if s.LastError != nil {
str += fmt.Sprintf(", Last error: %s", s.LastError)
}
return str
}
func decodeStringMap(element bson.RawElement, name string) (map[string]string, error) {
doc, ok := element.Value().DocumentOK()
if !ok {
return nil, fmt.Errorf("expected '%s' to be a document but it's a BSON %s", name, element.Value().Type)
}
elements, err := doc.Elements()
if err != nil {
return nil, err
}
m := make(map[string]string)
for _, element := range elements {
key := element.Key()
value, ok := element.Value().StringValueOK()
if !ok {
return nil, fmt.Errorf("expected '%s' to be a document of strings, but found a BSON %s", name, element.Value().Type)
}
m[key] = value
}
return m, nil
}
// Equal compares two server descriptions and returns true if they are equal
func (s Server) Equal(other Server) bool {
if s.CanonicalAddr.String() != other.CanonicalAddr.String() {
return false
}
if !sliceStringEqual(s.Arbiters, other.Arbiters) {
return false
}
if !sliceStringEqual(s.Hosts, other.Hosts) {
return false
}
if !sliceStringEqual(s.Passives, other.Passives) {
return false
}
if s.Primary != other.Primary {
return false
}
if s.SetName != other.SetName {
return false
}
if s.Kind != other.Kind {
return false
}
if s.LastError != nil || other.LastError != nil {
if s.LastError == nil || other.LastError == nil {
return false
}
if s.LastError.Error() != other.LastError.Error() {
return false
}
}
if !s.WireVersion.Equals(other.WireVersion) {
return false
}
if len(s.Tags) != len(other.Tags) || !s.Tags.ContainsAll(other.Tags) {
return false
}
if s.SetVersion != other.SetVersion {
return false
}
if s.ElectionID != other.ElectionID {
return false
}
if s.SessionTimeoutMinutes != other.SessionTimeoutMinutes {
return false
}
// If TopologyVersion is nil for both servers, CompareToIncoming will return -1 because it assumes that the
// incoming response is newer. We want the descriptions to be considered equal in this case, though, so an
// explicit check is required.
if s.TopologyVersion == nil && other.TopologyVersion == nil {
return true
}
return s.TopologyVersion.CompareToIncoming(other.TopologyVersion) == 0
}
func sliceStringEqual(a []string, b []string) bool {
if len(a) != len(b) {
return false
}
for i, v := range a {
if v != b[i] {
return false
}
}
return true
}

View File

@@ -0,0 +1,46 @@
// Copyright (C) MongoDB, Inc. 2017-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 description
// ServerKind represents the type of a single server in a topology.
type ServerKind uint32
// These constants are the possible types of servers.
const (
Standalone ServerKind = 1
RSMember ServerKind = 2
RSPrimary ServerKind = 4 + RSMember
RSSecondary ServerKind = 8 + RSMember
RSArbiter ServerKind = 16 + RSMember
RSGhost ServerKind = 32 + RSMember
Mongos ServerKind = 256
LoadBalancer ServerKind = 512
)
// String returns a stringified version of the kind or "Unknown" if the kind is invalid.
func (kind ServerKind) String() string {
switch kind {
case Standalone:
return "Standalone"
case RSMember:
return "RSOther"
case RSPrimary:
return "RSPrimary"
case RSSecondary:
return "RSSecondary"
case RSArbiter:
return "RSArbiter"
case RSGhost:
return "RSGhost"
case Mongos:
return "Mongos"
case LoadBalancer:
return "LoadBalancer"
}
return "Unknown"
}

View File

@@ -0,0 +1,341 @@
// Copyright (C) MongoDB, Inc. 2017-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 description
import (
"fmt"
"math"
"time"
"go.mongodb.org/mongo-driver/mongo/readpref"
"go.mongodb.org/mongo-driver/tag"
)
// ServerSelector is an interface implemented by types that can perform server selection given a topology description
// and list of candidate servers. The selector should filter the provided candidates list and return a subset that
// matches some criteria.
type ServerSelector interface {
SelectServer(Topology, []Server) ([]Server, error)
}
// ServerSelectorFunc is a function that can be used as a ServerSelector.
type ServerSelectorFunc func(Topology, []Server) ([]Server, error)
// SelectServer implements the ServerSelector interface.
func (ssf ServerSelectorFunc) SelectServer(t Topology, s []Server) ([]Server, error) {
return ssf(t, s)
}
type compositeSelector struct {
selectors []ServerSelector
}
// CompositeSelector combines multiple selectors into a single selector by applying them in order to the candidates
// list.
//
// For example, if the initial candidates list is [s0, s1, s2, s3] and two selectors are provided where the first
// matches s0 and s1 and the second matches s1 and s2, the following would occur during server selection:
//
// 1. firstSelector([s0, s1, s2, s3]) -> [s0, s1]
// 2. secondSelector([s0, s1]) -> [s1]
//
// The final list of candidates returned by the composite selector would be [s1].
func CompositeSelector(selectors []ServerSelector) ServerSelector {
return &compositeSelector{selectors: selectors}
}
func (cs *compositeSelector) SelectServer(t Topology, candidates []Server) ([]Server, error) {
var err error
for _, sel := range cs.selectors {
candidates, err = sel.SelectServer(t, candidates)
if err != nil {
return nil, err
}
}
return candidates, nil
}
type latencySelector struct {
latency time.Duration
}
// LatencySelector creates a ServerSelector which selects servers based on their average RTT values.
func LatencySelector(latency time.Duration) ServerSelector {
return &latencySelector{latency: latency}
}
func (ls *latencySelector) SelectServer(t Topology, candidates []Server) ([]Server, error) {
if ls.latency < 0 {
return candidates, nil
}
if t.Kind == LoadBalanced {
// In LoadBalanced mode, there should only be one server in the topology and it must be selected.
return candidates, nil
}
switch len(candidates) {
case 0, 1:
return candidates, nil
default:
min := time.Duration(math.MaxInt64)
for _, candidate := range candidates {
if candidate.AverageRTTSet {
if candidate.AverageRTT < min {
min = candidate.AverageRTT
}
}
}
if min == math.MaxInt64 {
return candidates, nil
}
max := min + ls.latency
var result []Server
for _, candidate := range candidates {
if candidate.AverageRTTSet {
if candidate.AverageRTT <= max {
result = append(result, candidate)
}
}
}
return result, nil
}
}
// WriteSelector selects all the writable servers.
func WriteSelector() ServerSelector {
return ServerSelectorFunc(func(t Topology, candidates []Server) ([]Server, error) {
switch t.Kind {
case Single, LoadBalanced:
return candidates, nil
default:
result := []Server{}
for _, candidate := range candidates {
switch candidate.Kind {
case Mongos, RSPrimary, Standalone:
result = append(result, candidate)
}
}
return result, nil
}
})
}
// ReadPrefSelector selects servers based on the provided read preference.
func ReadPrefSelector(rp *readpref.ReadPref) ServerSelector {
return readPrefSelector(rp, false)
}
// OutputAggregateSelector selects servers based on the provided read preference given that the underlying operation is
// aggregate with an output stage.
func OutputAggregateSelector(rp *readpref.ReadPref) ServerSelector {
return readPrefSelector(rp, true)
}
func readPrefSelector(rp *readpref.ReadPref, isOutputAggregate bool) ServerSelector {
return ServerSelectorFunc(func(t Topology, candidates []Server) ([]Server, error) {
if t.Kind == LoadBalanced {
// In LoadBalanced mode, there should only be one server in the topology and it must be selected. We check
// this before checking MaxStaleness support because there's no monitoring in this mode, so the candidate
// server wouldn't have a wire version set, which would result in an error.
return candidates, nil
}
if _, set := rp.MaxStaleness(); set {
for _, s := range candidates {
if s.Kind != Unknown {
if err := maxStalenessSupported(s.WireVersion); err != nil {
return nil, err
}
}
}
}
switch t.Kind {
case Single:
return candidates, nil
case ReplicaSetNoPrimary, ReplicaSetWithPrimary:
return selectForReplicaSet(rp, isOutputAggregate, t, candidates)
case Sharded:
return selectByKind(candidates, Mongos), nil
}
return nil, nil
})
}
// maxStalenessSupported returns an error if the given server version does not support max staleness.
func maxStalenessSupported(wireVersion *VersionRange) error {
if wireVersion != nil && wireVersion.Max < 5 {
return fmt.Errorf("max staleness is only supported for servers 3.4 or newer")
}
return nil
}
func selectForReplicaSet(rp *readpref.ReadPref, isOutputAggregate bool, t Topology, candidates []Server) ([]Server, error) {
if err := verifyMaxStaleness(rp, t); err != nil {
return nil, err
}
// If underlying operation is an aggregate with an output stage, only apply read preference
// if all candidates are 5.0+. Otherwise, operate under primary read preference.
if isOutputAggregate {
for _, s := range candidates {
if s.WireVersion.Max < 13 {
return selectByKind(candidates, RSPrimary), nil
}
}
}
switch rp.Mode() {
case readpref.PrimaryMode:
return selectByKind(candidates, RSPrimary), nil
case readpref.PrimaryPreferredMode:
selected := selectByKind(candidates, RSPrimary)
if len(selected) == 0 {
selected = selectSecondaries(rp, candidates)
return selectByTagSet(selected, rp.TagSets()), nil
}
return selected, nil
case readpref.SecondaryPreferredMode:
selected := selectSecondaries(rp, candidates)
selected = selectByTagSet(selected, rp.TagSets())
if len(selected) > 0 {
return selected, nil
}
return selectByKind(candidates, RSPrimary), nil
case readpref.SecondaryMode:
selected := selectSecondaries(rp, candidates)
return selectByTagSet(selected, rp.TagSets()), nil
case readpref.NearestMode:
selected := selectByKind(candidates, RSPrimary)
selected = append(selected, selectSecondaries(rp, candidates)...)
return selectByTagSet(selected, rp.TagSets()), nil
}
return nil, fmt.Errorf("unsupported mode: %d", rp.Mode())
}
func selectSecondaries(rp *readpref.ReadPref, candidates []Server) []Server {
secondaries := selectByKind(candidates, RSSecondary)
if len(secondaries) == 0 {
return secondaries
}
if maxStaleness, set := rp.MaxStaleness(); set {
primaries := selectByKind(candidates, RSPrimary)
if len(primaries) == 0 {
baseTime := secondaries[0].LastWriteTime
for i := 1; i < len(secondaries); i++ {
if secondaries[i].LastWriteTime.After(baseTime) {
baseTime = secondaries[i].LastWriteTime
}
}
var selected []Server
for _, secondary := range secondaries {
estimatedStaleness := baseTime.Sub(secondary.LastWriteTime) + secondary.HeartbeatInterval
if estimatedStaleness <= maxStaleness {
selected = append(selected, secondary)
}
}
return selected
}
primary := primaries[0]
var selected []Server
for _, secondary := range secondaries {
estimatedStaleness := secondary.LastUpdateTime.Sub(secondary.LastWriteTime) - primary.LastUpdateTime.Sub(primary.LastWriteTime) + secondary.HeartbeatInterval
if estimatedStaleness <= maxStaleness {
selected = append(selected, secondary)
}
}
return selected
}
return secondaries
}
func selectByTagSet(candidates []Server, tagSets []tag.Set) []Server {
if len(tagSets) == 0 {
return candidates
}
for _, ts := range tagSets {
// If this tag set is empty, we can take a fast path because the empty list is a subset of all tag sets, so
// all candidate servers will be selected.
if len(ts) == 0 {
return candidates
}
var results []Server
for _, s := range candidates {
// ts is non-empty, so only servers with a non-empty set of tags need to be checked.
if len(s.Tags) > 0 && s.Tags.ContainsAll(ts) {
results = append(results, s)
}
}
if len(results) > 0 {
return results
}
}
return []Server{}
}
func selectByKind(candidates []Server, kind ServerKind) []Server {
// Record the indices of viable candidates first and then append those to the returned slice
// to avoid appending costly Server structs directly as an optimization.
viableIndexes := make([]int, 0, len(candidates))
for i, s := range candidates {
if s.Kind == kind {
viableIndexes = append(viableIndexes, i)
}
}
result := make([]Server, len(viableIndexes))
for i, idx := range viableIndexes {
result[i] = candidates[idx]
}
return result
}
func verifyMaxStaleness(rp *readpref.ReadPref, t Topology) error {
maxStaleness, set := rp.MaxStaleness()
if !set {
return nil
}
if maxStaleness < 90*time.Second {
return fmt.Errorf("max staleness (%s) must be greater than or equal to 90s", maxStaleness)
}
if len(t.Servers) < 1 {
// Maybe we should return an error here instead?
return nil
}
// we'll assume all candidates have the same heartbeat interval.
s := t.Servers[0]
idleWritePeriod := 10 * time.Second
if maxStaleness < s.HeartbeatInterval+idleWritePeriod {
return fmt.Errorf(
"max staleness (%s) must be greater than or equal to the heartbeat interval (%s) plus idle write period (%s)",
maxStaleness, s.HeartbeatInterval, idleWritePeriod,
)
}
return nil
}

View File

@@ -0,0 +1,142 @@
// Copyright (C) MongoDB, Inc. 2017-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 description
import (
"fmt"
"go.mongodb.org/mongo-driver/mongo/readpref"
)
// Topology contains information about a MongoDB cluster.
type Topology struct {
Servers []Server
SetName string
Kind TopologyKind
SessionTimeoutMinutes uint32
CompatibilityErr error
}
// String implements the Stringer interface.
func (t Topology) String() string {
var serversStr string
for _, s := range t.Servers {
serversStr += "{ " + s.String() + " }, "
}
return fmt.Sprintf("Type: %s, Servers: [%s]", t.Kind, serversStr)
}
// Equal compares two topology descriptions and returns true if they are equal.
func (t Topology) Equal(other Topology) bool {
if t.Kind != other.Kind {
return false
}
topoServers := make(map[string]Server)
for _, s := range t.Servers {
topoServers[s.Addr.String()] = s
}
otherServers := make(map[string]Server)
for _, s := range other.Servers {
otherServers[s.Addr.String()] = s
}
if len(topoServers) != len(otherServers) {
return false
}
for _, server := range topoServers {
otherServer := otherServers[server.Addr.String()]
if !server.Equal(otherServer) {
return false
}
}
return true
}
// HasReadableServer returns true if the topology contains a server suitable for reading.
//
// If the Topology's kind is Single or Sharded, the mode parameter is ignored and the function contains true if any of
// the servers in the Topology are of a known type.
//
// For replica sets, the function returns true if the cluster contains a server that matches the provided read
// preference mode.
func (t Topology) HasReadableServer(mode readpref.Mode) bool {
switch t.Kind {
case Single, Sharded:
return hasAvailableServer(t.Servers, 0)
case ReplicaSetWithPrimary:
return hasAvailableServer(t.Servers, mode)
case ReplicaSetNoPrimary, ReplicaSet:
if mode == readpref.PrimaryMode {
return false
}
// invalid read preference
if !mode.IsValid() {
return false
}
return hasAvailableServer(t.Servers, mode)
}
return false
}
// HasWritableServer returns true if a topology has a server available for writing.
//
// If the Topology's kind is Single or Sharded, this function returns true if any of the servers in the Topology are of
// a known type.
//
// For replica sets, the function returns true if the replica set contains a primary.
func (t Topology) HasWritableServer() bool {
return t.HasReadableServer(readpref.PrimaryMode)
}
// hasAvailableServer returns true if any servers are available based on the read preference.
func hasAvailableServer(servers []Server, mode readpref.Mode) bool {
switch mode {
case readpref.PrimaryMode:
for _, s := range servers {
if s.Kind == RSPrimary {
return true
}
}
return false
case readpref.PrimaryPreferredMode, readpref.SecondaryPreferredMode, readpref.NearestMode:
for _, s := range servers {
if s.Kind == RSPrimary || s.Kind == RSSecondary {
return true
}
}
return false
case readpref.SecondaryMode:
for _, s := range servers {
if s.Kind == RSSecondary {
return true
}
}
return false
}
// read preference is not specified
for _, s := range servers {
switch s.Kind {
case Standalone,
RSMember,
RSPrimary,
RSSecondary,
RSArbiter,
RSGhost,
Mongos:
return true
}
}
return false
}

View File

@@ -0,0 +1,40 @@
// Copyright (C) MongoDB, Inc. 2017-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 description
// TopologyKind represents a specific topology configuration.
type TopologyKind uint32
// These constants are the available topology configurations.
const (
Single TopologyKind = 1
ReplicaSet TopologyKind = 2
ReplicaSetNoPrimary TopologyKind = 4 + ReplicaSet
ReplicaSetWithPrimary TopologyKind = 8 + ReplicaSet
Sharded TopologyKind = 256
LoadBalanced TopologyKind = 512
)
// String implements the fmt.Stringer interface.
func (kind TopologyKind) String() string {
switch kind {
case Single:
return "Single"
case ReplicaSet:
return "ReplicaSet"
case ReplicaSetNoPrimary:
return "ReplicaSetNoPrimary"
case ReplicaSetWithPrimary:
return "ReplicaSetWithPrimary"
case Sharded:
return "Sharded"
case LoadBalanced:
return "LoadBalanced"
}
return "Unknown"
}

View File

@@ -0,0 +1,66 @@
// Copyright (C) MongoDB, Inc. 2017-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 description
import (
"fmt"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
)
// TopologyVersion represents a software version.
type TopologyVersion struct {
ProcessID primitive.ObjectID
Counter int64
}
// NewTopologyVersion creates a TopologyVersion based on doc
func NewTopologyVersion(doc bson.Raw) (*TopologyVersion, error) {
elements, err := doc.Elements()
if err != nil {
return nil, err
}
var tv TopologyVersion
var ok bool
for _, element := range elements {
switch element.Key() {
case "processId":
tv.ProcessID, ok = element.Value().ObjectIDOK()
if !ok {
return nil, fmt.Errorf("expected 'processId' to be a objectID but it's a BSON %s", element.Value().Type)
}
case "counter":
tv.Counter, ok = element.Value().Int64OK()
if !ok {
return nil, fmt.Errorf("expected 'counter' to be an int64 but it's a BSON %s", element.Value().Type)
}
}
}
return &tv, nil
}
// CompareToIncoming compares the receiver, which represents the currently known TopologyVersion for a server, to an
// incoming TopologyVersion extracted from a server command response.
//
// This returns -1 if the receiver version is less than the response, 0 if the versions are equal, and 1 if the
// receiver version is greater than the response. This comparison is not commutative.
func (tv *TopologyVersion) CompareToIncoming(responseTV *TopologyVersion) int {
if tv == nil || responseTV == nil {
return -1
}
if tv.ProcessID != responseTV.ProcessID {
return -1
}
if tv.Counter == responseTV.Counter {
return 0
}
if tv.Counter < responseTV.Counter {
return -1
}
return 1
}

View File

@@ -0,0 +1,42 @@
// Copyright (C) MongoDB, Inc. 2017-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 description
import "fmt"
// VersionRange represents a range of versions.
type VersionRange struct {
Min int32
Max int32
}
// NewVersionRange creates a new VersionRange given a min and a max.
func NewVersionRange(min, max int32) VersionRange {
return VersionRange{Min: min, Max: max}
}
// Includes returns a bool indicating whether the supplied integer is included
// in the range.
func (vr VersionRange) Includes(v int32) bool {
return v >= vr.Min && v <= vr.Max
}
// Equals returns a bool indicating whether the supplied VersionRange is equal.
func (vr *VersionRange) Equals(other *VersionRange) bool {
if vr == nil && other == nil {
return true
}
if vr == nil || other == nil {
return false
}
return vr.Min == other.Min && vr.Max == other.Max
}
// String implements the fmt.Stringer interface.
func (vr VersionRange) String() string {
return fmt.Sprintf("[%d, %d]", vr.Min, vr.Max)
}