newline battles continue

This commit is contained in:
bel
2020-01-19 20:41:30 +00:00
parent 98adb53caf
commit 991c27d044
1457 changed files with 525871 additions and 6 deletions

View File

@@ -0,0 +1,10 @@
// 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 "go.mongodb.org/mongo-driver/x/mongo/driver/description"
// Unknown is an unknown server or topology kind.
const Unknown = 0

View File

@@ -0,0 +1,36 @@
// 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"
)
// 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
}
// ScramSHA1Supported returns an error if the given server version
// does not support scram-sha-1.
func ScramSHA1Supported(wireVersion *VersionRange) error {
if wireVersion != nil && wireVersion.Max < 3 {
return fmt.Errorf("SCRAM-SHA-1 is only supported for servers 3.0 or newer")
}
return nil
}
// SessionsSupported returns true of the given server version indicates that it supports sessions.
func SessionsSupported(wireVersion *VersionRange) bool {
return wireVersion != nil && wireVersion.Max >= 6
}

View File

@@ -0,0 +1,154 @@
// 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"
"time"
"go.mongodb.org/mongo-driver/bson/primitive"
"go.mongodb.org/mongo-driver/tag"
"go.mongodb.org/mongo-driver/x/mongo/driver/address"
"go.mongodb.org/mongo-driver/x/network/result"
)
// UnsetRTT is the unset value for a round trip time.
const UnsetRTT = -1 * time.Millisecond
// SelectedServer represents a selected server that is a member of a topology.
type SelectedServer struct {
Server
Kind TopologyKind
}
// Server represents a description of a server. This is created from an isMaster
// command.
type Server struct {
Addr address.Address
AverageRTT time.Duration
AverageRTTSet bool
Compression []string // compression methods returned by server
CanonicalAddr address.Address
ElectionID primitive.ObjectID
HeartbeatInterval time.Duration
LastError error
LastUpdateTime time.Time
LastWriteTime time.Time
MaxBatchCount uint32
MaxDocumentSize uint32
MaxMessageSize uint32
Members []address.Address
ReadOnly bool
SessionTimeoutMinutes uint32
SetName string
SetVersion uint32
Tags tag.Set
Kind ServerKind
WireVersion *VersionRange
SaslSupportedMechs []string // user-specific from server handshake
}
// NewServer creates a new server description from the given parameters.
func NewServer(addr address.Address, isMaster result.IsMaster) Server {
i := Server{
Addr: addr,
CanonicalAddr: address.Address(isMaster.Me).Canonicalize(),
Compression: isMaster.Compression,
ElectionID: isMaster.ElectionID,
LastUpdateTime: time.Now().UTC(),
LastWriteTime: isMaster.LastWriteTimestamp,
MaxBatchCount: isMaster.MaxWriteBatchSize,
MaxDocumentSize: isMaster.MaxBSONObjectSize,
MaxMessageSize: isMaster.MaxMessageSizeBytes,
SaslSupportedMechs: isMaster.SaslSupportedMechs,
SessionTimeoutMinutes: isMaster.LogicalSessionTimeoutMinutes,
SetName: isMaster.SetName,
SetVersion: isMaster.SetVersion,
Tags: tag.NewTagSetFromMap(isMaster.Tags),
}
if i.CanonicalAddr == "" {
i.CanonicalAddr = addr
}
if isMaster.OK != 1 {
i.LastError = fmt.Errorf("not ok")
return i
}
for _, host := range isMaster.Hosts {
i.Members = append(i.Members, address.Address(host).Canonicalize())
}
for _, passive := range isMaster.Passives {
i.Members = append(i.Members, address.Address(passive).Canonicalize())
}
for _, arbiter := range isMaster.Arbiters {
i.Members = append(i.Members, address.Address(arbiter).Canonicalize())
}
i.Kind = Standalone
if isMaster.IsReplicaSet {
i.Kind = RSGhost
} else if isMaster.SetName != "" {
if isMaster.IsMaster {
i.Kind = RSPrimary
} else if isMaster.Hidden {
i.Kind = RSMember
} else if isMaster.Secondary {
i.Kind = RSSecondary
} else if isMaster.ArbiterOnly {
i.Kind = RSArbiter
} else {
i.Kind = RSMember
}
} else if isMaster.Msg == "isdbgrid" {
i.Kind = Mongos
}
i.WireVersion = &VersionRange{
Min: isMaster.MinWireVersion,
Max: isMaster.MaxWireVersion,
}
return i
}
// SetAverageRTT sets the average round trip time for this server description.
func (s Server) SetAverageRTT(rtt time.Duration) Server {
s.AverageRTT = rtt
if rtt == UnsetRTT {
s.AverageRTTSet = false
} else {
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
}
// SelectServer selects this server if it is in the list of given candidates.
func (s Server) SelectServer(_ Topology, candidates []Server) ([]Server, error) {
for _, candidate := range candidates {
if candidate.Addr == s.Addr {
return []Server{candidate}, nil
}
}
return nil, nil
}

View File

@@ -0,0 +1,43 @@
// 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 server.
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
)
// String implements the fmt.Stringer interface.
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"
}
return "Unknown"
}

View File

@@ -0,0 +1,279 @@
// 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 select a server given a
// topology description.
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.
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 latency.
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
}
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:
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 ServerSelectorFunc(func(t Topology, candidates []Server) ([]Server, error) {
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, t, candidates)
case Sharded:
return selectByKind(candidates, Mongos), nil
}
return nil, nil
})
}
func selectForReplicaSet(rp *readpref.ReadPref, t Topology, candidates []Server) ([]Server, error) {
if err := verifyMaxStaleness(rp, t); err != nil {
return nil, err
}
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 {
var results []Server
for _, s := range candidates {
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 {
var result []Server
for _, s := range candidates {
if s.Kind == kind {
result = append(result, s)
}
}
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,136 @@
// 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 (
"sort"
"strings"
"go.mongodb.org/mongo-driver/x/mongo/driver/address"
)
// Topology represents a description of a mongodb topology
type Topology struct {
Servers []Server
Kind TopologyKind
SessionTimeoutMinutes uint32
}
// Server returns the server for the given address. Returns false if the server
// could not be found.
func (t Topology) Server(addr address.Address) (Server, bool) {
for _, server := range t.Servers {
if server.Addr.String() == addr.String() {
return server, true
}
}
return Server{}, false
}
// TopologyDiff is the difference between two different topology descriptions.
type TopologyDiff struct {
Added []Server
Removed []Server
}
// DiffTopology compares the two topology descriptions and returns the difference.
func DiffTopology(old, new Topology) TopologyDiff {
var diff TopologyDiff
// TODO: do this without sorting...
oldServers := serverSorter(old.Servers)
newServers := serverSorter(new.Servers)
sort.Sort(oldServers)
sort.Sort(newServers)
i := 0
j := 0
for {
if i < len(oldServers) && j < len(newServers) {
comp := strings.Compare(oldServers[i].Addr.String(), newServers[j].Addr.String())
switch comp {
case 1:
//left is bigger than
diff.Added = append(diff.Added, newServers[j])
j++
case -1:
// right is bigger
diff.Removed = append(diff.Removed, oldServers[i])
i++
case 0:
i++
j++
}
} else if i < len(oldServers) {
diff.Removed = append(diff.Removed, oldServers[i])
i++
} else if j < len(newServers) {
diff.Added = append(diff.Added, newServers[j])
j++
} else {
break
}
}
return diff
}
// HostlistDiff is the difference between a topology and a host list.
type HostlistDiff struct {
Added []string
Removed []string
}
// DiffHostlist compares the topology description and host list and returns the difference.
func (t Topology) DiffHostlist(hostlist []string) HostlistDiff {
var diff HostlistDiff
oldServers := serverSorter(t.Servers)
sort.Sort(oldServers)
sort.Strings(hostlist)
i := 0
j := 0
for {
if i < len(oldServers) && j < len(hostlist) {
oldServer := oldServers[i].Addr.String()
comp := strings.Compare(oldServer, hostlist[j])
switch comp {
case 1:
// oldServers[i] is bigger
diff.Added = append(diff.Added, hostlist[j])
j++
case -1:
// hostlist[j] is bigger
diff.Removed = append(diff.Removed, oldServer)
i++
case 0:
i++
j++
}
} else if i < len(oldServers) {
diff.Removed = append(diff.Removed, oldServers[i].Addr.String())
i++
} else if j < len(hostlist) {
diff.Added = append(diff.Added, hostlist[j])
j++
} else {
break
}
}
return diff
}
type serverSorter []Server
func (ss serverSorter) Len() int { return len(ss) }
func (ss serverSorter) Swap(i, j int) { ss[i], ss[j] = ss[j], ss[i] }
func (ss serverSorter) Less(i, j int) bool {
return strings.Compare(ss[i].Addr.String(), ss[j].Addr.String()) < 0
}

View File

@@ -0,0 +1,37 @@
// 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
)
// 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"
}
return "Unknown"
}

View File

@@ -0,0 +1,44 @@
// 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 "strconv"
// Version represents a software version.
type Version struct {
Desc string
Parts []uint8
}
// AtLeast ensures that the version is at least as large as the "other" version.
func (v Version) AtLeast(other ...uint8) bool {
for i := range other {
if i == len(v.Parts) {
return false
}
if v.Parts[i] < other[i] {
return false
}
}
return true
}
// String provides the string represtation of the Version.
func (v Version) String() string {
if v.Desc == "" {
var s string
for i, p := range v.Parts {
if i != 0 {
s += "."
}
s += strconv.Itoa(int(p))
}
return s
}
return v.Desc
}

View File

@@ -0,0 +1,31 @@
// 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
}
// String implements the fmt.Stringer interface.
func (vr VersionRange) String() string {
return fmt.Sprintf("[%d, %d]", vr.Min, vr.Max)
}