Skip to content

Commit 8f5e116

Browse files
authored
SWIFT-1435: find suitable servers function (#724)
1 parent 3f3b9c4 commit 8f5e116

File tree

2 files changed

+151
-2
lines changed

2 files changed

+151
-2
lines changed

Sources/MongoSwift/SDAM.swift

Lines changed: 84 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,28 @@ public struct ServerDescription {
223223
} ?? []
224224
self.tags = hello?.tags ?? [:]
225225
}
226+
227+
// For testing purposes
228+
internal init(type: ServerType) {
229+
self.type = type
230+
self.address = ServerAddress(host: "fake", port: 80)
231+
self.serverId = 0
232+
self.roundTripTime = 0
233+
self.lastUpdateTime = Date()
234+
self.lastWriteDate = nil
235+
self.minWireVersion = 0
236+
self.maxWireVersion = 0
237+
self.me = self.address
238+
self.setName = nil
239+
self.setVersion = nil
240+
self.electionID = nil
241+
self.primary = nil
242+
self.logicalSessionTimeoutMinutes = nil
243+
self.hosts = []
244+
self.passives = []
245+
self.arbiters = []
246+
self.tags = [:]
247+
}
226248
}
227249

228250
extension ServerDescription: Equatable {
@@ -270,7 +292,7 @@ public struct TopologyDescription: Equatable {
270292

271293
/// Internal representation of topology type. If enums could be marked non-exhaustive in Swift, this would be
272294
/// the public representation too.
273-
private enum _TopologyType: String, Equatable {
295+
fileprivate enum _TopologyType: String, Equatable {
274296
case single = "Single"
275297
case replicaSetNoPrimary = "ReplicaSetNoPrimary"
276298
case replicaSetWithPrimary = "ReplicaSetWithPrimary"
@@ -279,7 +301,7 @@ public struct TopologyDescription: Equatable {
279301
case loadBalanced = "LoadBalanced"
280302
}
281303

282-
private let _topologyType: _TopologyType
304+
fileprivate let _topologyType: _TopologyType
283305

284306
private init(_ _type: _TopologyType) {
285307
self._topologyType = _type
@@ -353,4 +375,64 @@ public struct TopologyDescription: Equatable {
353375
self.servers = size > 0 ? Array(buffer).map { ServerDescription($0!) } : []
354376
// the buffer is documented as always containing non-nil pointers (if non-empty).
355377
}
378+
379+
// For testing purposes
380+
internal init(type: TopologyType, servers: [ServerDescription]) {
381+
self.type = type
382+
self.servers = servers
383+
}
384+
}
385+
386+
extension TopologyDescription {
387+
internal func findSuitableServers(readPreference: ReadPreference? = nil) -> [ServerDescription] {
388+
switch self.type._topologyType {
389+
case .unknown:
390+
return []
391+
case .single,
392+
.loadBalanced:
393+
return self.servers
394+
case .replicaSetNoPrimary,
395+
.replicaSetWithPrimary:
396+
switch readPreference?.mode {
397+
case .secondary:
398+
let secondaries = self.servers.filter { $0.type == .rsSecondary }
399+
return self.filterReplicaSetServers(readPreference: readPreference, servers: secondaries)
400+
case .nearest:
401+
let secondariesAndPrimary = self.servers.filter { $0.type == .rsSecondary || $0.type == .rsPrimary }
402+
return self.filterReplicaSetServers(readPreference: readPreference, servers: secondariesAndPrimary)
403+
case .secondaryPreferred:
404+
// If mode is 'secondaryPreferred', attempt the selection algorithm with mode 'secondary' and the
405+
// user's maxStalenessSeconds and tag_sets.If no server matches, select the primary.
406+
let secondaries = self.servers.filter { $0.type == .rsSecondary }
407+
let primaries = self.servers.filter { $0.type == .rsPrimary }
408+
let matches = self.filterReplicaSetServers(readPreference: readPreference, servers: secondaries)
409+
return matches.isEmpty ? primaries : matches
410+
case .primaryPreferred:
411+
// If mode is 'primaryPreferred' or a readPreference is not provided, select the primary if it is known,
412+
// otherwise attempt the selection algorithm with mode 'secondary' and the user's
413+
// maxStalenessSeconds and tag_sets.
414+
let primaries = self.servers.filter { $0.type == .rsPrimary }
415+
if !primaries.isEmpty {
416+
return primaries
417+
}
418+
let secondaries = self.servers.filter { $0.type == .rsSecondary }
419+
return self.filterReplicaSetServers(readPreference: readPreference, servers: secondaries)
420+
default:
421+
// the default mode is 'primary'.
422+
return self.servers.filter { $0.type == .rsPrimary }
423+
}
424+
case .sharded:
425+
return self.servers.filter { $0.type == .mongos }
426+
}
427+
}
428+
429+
internal func filterReplicaSetServers(
430+
readPreference _: ReadPreference?,
431+
servers: [ServerDescription]
432+
) -> [ServerDescription] {
433+
// TODO: Filter out servers staler than maxStalenessSeconds
434+
// TODO: Select servers matching the tag_sets
435+
// While waiting for the above to be implemented, this helper just returns the servers it was passed
436+
servers
437+
}
356438
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import Foundation
2+
@testable import MongoSwift
3+
import Nimble
4+
import NIO
5+
import TestsCommon
6+
import XCTest
7+
8+
final class ServerSelectionTests: MongoSwiftTestCase {
9+
func testServerSelection() throws {
10+
let standaloneServer = ServerDescription(type: .standalone)
11+
let rsPrimaryServer = ServerDescription(type: .rsPrimary)
12+
let rsSecondaryServer1 = ServerDescription(type: .rsSecondary)
13+
let rsSecondaryServer2 = ServerDescription(type: .rsSecondary)
14+
let mongosServer = ServerDescription(type: .mongos)
15+
16+
// unknown
17+
let unkownTopology = TopologyDescription(type: .unknown, servers: [standaloneServer])
18+
expect(unkownTopology.findSuitableServers()).to(haveCount(0))
19+
20+
// single
21+
let singleTopology = TopologyDescription(type: .single, servers: [standaloneServer])
22+
expect(singleTopology.findSuitableServers()[0].type).to(equal(.standalone))
23+
24+
// replica set with primary
25+
let replicaSetTopology = TopologyDescription(type: .replicaSetWithPrimary, servers: [
26+
rsPrimaryServer,
27+
rsSecondaryServer1,
28+
rsSecondaryServer2
29+
])
30+
let primaryReadPreference = ReadPreference(.primary)
31+
let replicaSetSuitableServers = replicaSetTopology.findSuitableServers(readPreference: primaryReadPreference)
32+
expect(replicaSetSuitableServers[0].type).to(equal(.rsPrimary))
33+
expect(replicaSetSuitableServers).to(haveCount(1))
34+
35+
let primaryPrefReadPreferemce = ReadPreference(.primaryPreferred)
36+
let replicaSetSuitableServers2 = replicaSetTopology
37+
.findSuitableServers(readPreference: primaryPrefReadPreferemce)
38+
expect(replicaSetSuitableServers2[0].type).to(equal(.rsPrimary))
39+
expect(replicaSetSuitableServers2).to(haveCount(1))
40+
41+
// replica set without primary
42+
let replicaSetNoPrimaryTopology = TopologyDescription(type: .replicaSetNoPrimary, servers: [
43+
rsSecondaryServer1,
44+
rsSecondaryServer2
45+
])
46+
let replicaSetNoPrimarySuitableServers = replicaSetNoPrimaryTopology
47+
.findSuitableServers(readPreference: primaryReadPreference)
48+
expect(replicaSetNoPrimarySuitableServers).to(haveCount(0))
49+
50+
let replicaSetNoPrimarySuitableServer2 = replicaSetNoPrimaryTopology.findSuitableServers(readPreference: nil)
51+
expect(replicaSetNoPrimarySuitableServer2).to(haveCount(0))
52+
53+
let replicaSetNoPrimarySuitableServer3 = replicaSetNoPrimaryTopology
54+
.findSuitableServers(readPreference: primaryPrefReadPreferemce)
55+
expect(replicaSetNoPrimarySuitableServer3[0].type).to(equal(.rsSecondary))
56+
expect(replicaSetNoPrimarySuitableServer3).to(haveCount(2))
57+
58+
// sharded
59+
let shardedTopology = TopologyDescription(type: .sharded, servers: [
60+
mongosServer
61+
])
62+
let shardedSuitableServers = shardedTopology.findSuitableServers()
63+
expect(shardedSuitableServers[0].type)
64+
.to(equal(.mongos))
65+
expect(shardedSuitableServers).to(haveCount(1))
66+
}
67+
}

0 commit comments

Comments
 (0)